import hashlib
from enum import Enum
from typing import Dict, List, Optional, Union

from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator


class ToneType(str, Enum):
    """回复口吻类型"""
    STANDARD = "standard"  # 标准专业口吻
    FRIENDLY = "friendly"  # 亲切友好口吻
    PATIENT = "patient"    # 耐心细致口吻
    TRENDY = "trendy"      # 网感新颖口吻

from .dify import DifyKnowledge
from .knowledge import Graph, LabelType


def create_hash_id(input_string: str) -> str:
    """
    Generate a hash ID for a given string using SHA-256.

    Args:
        input_string (str): The input string to hash.

    Returns:
        str: A hash ID (shortened to 24 characters for simplicity).
    """
    # Compute SHA-256 hash of the input string
    sha256_hash = hashlib.sha256(input_string.encode()).hexdigest()
    # Shorten the hash to 8 characters
    hash_id = sha256_hash[:24]
    return hash_id


class WorkflowRequest(BaseModel):
    @field_validator("user_input", mode="before")
    def validate_query(cls, value):
        return value.strip()

    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "conversation_id": "idxxx",
                "tenant_id": "idxxx",
                "channel_id": "idxxx",
                "user_input": "你好",
                "reset": False,
                "use_qa_as_example": True,
                "use_llm_for_unknown": False,
            }
        }
    )

    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    channel_id: str = Field(description="渠道id", examples=["idxxx"])
    conversation_id: str = Field(description="会话id", examples=["idxxx"])
    user_input: str = Field(description="当前用户输入", examples=["你好"])
    reset: bool = Field(False, description="是否重置会话", examples=[True, False])
    agent_name: Optional[str] = Field(
        None, description="机器人名称", examples=["社保姐姐"]
    )
    position: Optional[str] = Field(None, description="省市", examples=["广东省东莞市"])
    use_qa_as_example: Optional[bool] = Field(
        True, description="是否使用QA作为示例", examples=[True, False]
    )
    use_llm_for_unknown: Optional[bool] = Field(
        False,
        description="是否使用大模型回答未知问题和超纲问题",
        examples=[True, False],
    )
    strict_mode: Optional[bool] = Field(
        False,
        description="是否严格模式,严格模式下,会强制要求模型复述答案，而不是根据答案回答",
        examples=[True, False],
    )
    service: Optional[str] = Field(
        None, description="助手服务范围", examples=["可以回答养老保险、医疗保险等相关问题"]
    )
    tone: Optional[ToneType] = Field(
        ToneType.STANDARD, description="回复口吻类型", examples=["standard", "friendly", "patient", "trendy"]
    )

    @field_validator("use_qa_as_example", mode="before")
    def validate_use_qa_as_example(cls, value):
        if isinstance(value, str):
            return value.lower() == "true"
        return value

    @field_validator("use_llm_for_unknown", mode="before")
    def validate_use_llm_for_unknown(cls, value):
        if isinstance(value, str):
            return value.lower() == "true"
        return value

    @field_validator("strict_mode", mode="before")
    def validate_strict_mode(cls, value):
        if isinstance(value, str):
            return value.lower() == "true"
        return value


class WorkflowByAgentRequest(BaseModel):
    agent_name: Optional[str] = Field(
        None, description="助手名称", examples=["社保姐姐"]
    )
    channel_id: Optional[str] = Field(
        None, description="渠道id", examples=["channel_123"]
    )
    user_input: str = Field(description="当前用户输入", examples=["你好"])
    conversation_id: str = Field(description="会话id", examples=["idxxx"])
    reset: bool = Field(False, description="是否重置会话", examples=[True, False])
    use_qa_as_example: bool = Field(
        True, description="是否使用QA作为示例", examples=[True, False]
    )
    use_llm_for_unknown: bool = Field(
        False,
        description="是否使用大模型回答未知问题和超纲问题",
        examples=[True, False],
    )
    strict_mode: bool = Field(False, description="是否严格模式", examples=[True, False])
    tone: Optional[ToneType] = Field(
        ToneType.STANDARD, description="回复口吻类型", examples=["standard", "friendly", "patient", "trendy"]
    )

    @model_validator(mode="after")
    def check_exactly_one(cls, values):
        if not values.agent_name and not values.channel_id:
            raise ValueError("Either agent_name or channel_id must be provided")
        if values.agent_name and values.channel_id:
            raise ValueError("Only one of agent_name or channel_id should be provided")
        return values


class WorkflowResponse(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "system_prompt": "系统提示词",
                "user_prompt": "用户提示词",
                "knowledge": [
                    {
                        "title": "东莞QA",
                        "content": "养老保险-关系转移-企业职工-灵活就业人员-跨省（广东省外）-关系转入-办理指南\n企业职工（含灵活就业人员）养老保险跨省如何转移？...",
                        "metadata": {
                            "_source": "knowledge",
                            "position": 1,
                            "dataset_id": "30a66f85-9a34-4807-821a-84bf98799cc5",
                            "dataset_name": "东莞QA",
                            "document_id": "0504b66d-824c-4f2a-abbf-ea0f66bf47e5",
                            "document_name": "东莞QA",
                            "document_data_source_type": "upload_file",
                            "segment_id": "7c667389-ca86-91ab-2adf-ee119d50daa5",
                            "retriever_from": "workflow",
                            "score": 0.9996366500854492,
                            "segment_hit_count": 1885,
                            "segment_word_count": 1125,
                            "segment_position": 289,
                            "segment_index_node_hash": "1540acad2146b16b5dbcf7b253dae28d51481d1f271b9ebdadfb9d5e40d6bcae",
                        },
                    }
                ],
                "graph": {
                    "level_1": ["养老保险"],
                    "level_2": ["转移"],
                    "scene": [
                        {"label": "跨省", "category": "户籍与地域"},
                        {"label": "城乡居民", "category": "参保人身份"},
                    ],
                    "keywords": [],
                },
                "calculation_params": {
                    "years_of_payment": 4.5,
                    "previous_unpaid_months": 2,
                    "min_wage": 2200,
                },
                "deepthink": False,
            }
        }
    )

    system_prompt: str = Field(description="系统提示词")
    user_prompt: str = Field(description="用户提示词")
    knowledge: List[DifyKnowledge] = Field(description="相关知识信息", default=[])
    graph: Optional[Graph] = Field(description="当前对话的图谱信息", default=None)
    calculation_params: Optional[Dict] = Field(description="计算参数", default=None)
    deepthink: bool = Field(description="是否触发深度思考", default=False)
    status_code: str = Field(description="状态码", default="success")


class ExtractionConfig(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "prompt": "抽取提示词",
                "failure_default": {"参数名1": "", "参数名2": ""},
                "reask_words": {"参数1": "请问xxx", "参数2": "请问xxx"},
            }
        }
    )

    prompt: str = Field("", description="抽取提示词")
    failure_default: Dict[str, str] = Field(
        {},
        description="抽取失败时默认返回的参数",
        examples=[{"参数名1": "", "参数名2": ""}],
    )
    reask_words: Dict[str, str] = Field(
        {},
        description="某些必填字段的反问话术",
        examples=[{"参数1": "请问xxx", "参数2": "请问xxx"}],
    )


class Knowledge(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "dataset_id": "idxxx",
                "segment_id": "idxxx",
                "tenant_id": "idxxx",
                "channel_id": ["idxxx"],
                "question": "社保征缴相关问题指引口径？",
                "answer": "您好，在办理社保缴费登记、申报社保缴费业务时如有疑问，可拨打0769-12366纳税缴费服务热线咨询...",
                "extraction": {
                    "prompt": "",
                    "failure_default": {"参数名1": "", "参数名2": ""},
                    "reask_words": {"参数1": "请问xxx", "参数2": "请问xxx"},
                },
                "graph": {
                    "level_1": ["养老保险"],
                    "level_2": ["转移"],
                    "scene": [
                        {"label": "跨省", "category": "户籍与地域"},
                        {"label": "城乡居民", "category": "参保人身份"},
                    ],
                    "keywords": [],
                },
            }
        }
    )

    dataset_id: str = Field(description="数据集id,用于区分不同的知识图谱")
    segment_id: str = Field(description="知识片段id")
    desheng_id: Optional[str] = Field(None, description="德胜id")
    tenant_id: str = Field(description="租户id")
    channel_id: List[str] = Field(description="渠道id")
    question: str = Field(description="问题文本")
    answer: str = Field(description="答案文本")
    supplementary: Optional[str] = Field(
        "",
        description="补充信息，可选，当知识类型为计算类型时，可以添加补充说明来帮助计算",
    )
    graph: Graph = Field(description="知识图谱信息")
    extraction: Optional[ExtractionConfig] = Field(
        None, description="抽取配置，包含prompt、failure_default和reask_words"
    )
    ktype: Optional[str] = Field(None, description="知识类型")
    matched_score: Optional[float] = Field(None, description="匹配得分", examples=[0.9])

    # handle channel_id, if channel_id is a string ,convert it to a list
    @field_validator("channel_id", mode="before")
    def validate_channel_id(cls, value):
        if isinstance(value, str):
            return [value]
        else:
            return value

    @field_validator("ktype", mode="before")
    def validate_ktype(cls, value):
        if not value or not isinstance(value, str):
            return "normal"
        if value not in ["normal", "goodcase", "badcase", "calculator", "information"]:
            raise ValueError(
                "ktype must be normal, goodcase, badcase, calculator or information"
            )
        return value

    def to_str_context(self) -> str:
        """
        Convert the knowledge data to a string context.

        Returns:
            str: The string context.
        """
        return f"""<knowledge>
    <question>{self.question}</question>
    <answer>{self.answer}</answer>
    <supplementary>{self.supplementary}</supplementary>
</knowledge>"""

    @model_validator(mode="after")
    def validate(cls, values: "Knowledge"):
        if not values.desheng_id:
            values.desheng_id = values.segment_id
        return values


class RefuseWords(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "channel_id": ["idxxx"],
                "tenant_id": "idxxx",
                "refuse_id": "idxxx",
                "words": [
                    "对不起，我现在无法回答你的问题",
                    "抱歉，我现在无法回答你的问题",
                ],
            },
            "description": "添加或更新拒答话术",
        }
    )

    channel_id: List[str] = Field(description="渠道id", examples=[["idxxx", "idyyy"]])
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    refuse_id: str = Field(
        "", description="拒答话术id,用于区分不同的拒答话术", examples=["idxxx"]
    )
    words: List[str] = Field(
        description="拒答话术列表",
        examples=[
            [
                "请问你以企业职工、灵活就业人员、城乡居民、机关事业单位职工那种身份参保呢？"
            ]
        ],
    )

    @model_validator(mode="after")
    def validate(cls, values: "RefuseWords"):
        if not values.refuse_id:
            channel_id_str = "-".join(sorted(values.channel_id))
            refuse_id = create_hash_id(f"refuse-{channel_id_str}-{values.tenant_id}")
            values.refuse_id = refuse_id

        return values


class RefuseWordsExclusion(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "refuse_exclusion_id": "idxxx",
                "tenant_id": "idxxx",
                "channel_id": ["idxxx"],
                "category": "问候语",
                "examples": ["你好", "您好"],
                "definition": "问候语",
                "words": [
                    "你好，很高兴为您服务，请问有什么可以帮到您",
                    "您好，很高兴为您服务",
                ],
            },
            "description": "添加或更新拒答话术例外情况",
        }
    )

    refuse_exclusion_id: str = Field(
        "", description="拒答话术例外情况id", examples=["idxxx"]
    )
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    channel_id: List[str] = Field(
        description="渠道id，可以有多个渠道", examples=[["idxxx"]]
    )
    category: str = Field(description="排除拒答话术的场景引导类别", examples=["问候语"])
    examples: List[str] = Field(
        description="该类别请求示例", examples=[["你好", "您好"]]
    )
    definition: str = Field(description="类别定义说明", examples=["问候语"])
    words: List[str] = Field(
        description="答案参照，模型回答参照", examples=[["你好，很高兴为您服务"]]
    )

    @model_validator(mode="after")
    def validate(cls, values: "RefuseWordsExclusion"):
        if not values.refuse_exclusion_id:
            channel_id_str = "-".join(sorted(values.channel_id))
            refuse_exclusion_id = create_hash_id(
                f"refuse-exclusion-{channel_id_str}-{values.tenant_id}-{values.category}"
            )
            values.refuse_exclusion_id = refuse_exclusion_id

        return values


class DeleteRequest(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {"id_type": "dataset", "id": ["idxxx", "idyyy"]},
            "description": "删除数据集、知识片段、解释说明、澄清话术、拒答话术、助手信息，支持批量删除",
        }
    )

    class IDType(str, Enum):
        DATASET = "dataset"
        SEGMENT = "segment"
        EXPLANATION = "explanation"
        CLARIFY = "clarify"
        REFUSE_WORDS = "refuse_words"
        REFUSE_WORDS_EXCLUSION = "refuse_words_exclusion"
        ASSISTANT = "assistant"
        SIMPLE_ANSWER_PROMPT = "simple_answer_prompt"

    id_type: IDType = Field(description="删除类型", examples=["dataset", "segment"])
    id: Union[str, List[str]] = Field(
        description="删除id，可以是单个id字符串或id列表进行批量删除",
        examples=["idxxx", ["idxxx", "idyyy"]],
    )

    @field_validator("id", mode="before")
    def validate_id(cls, value):
        """
        Ensure id is converted to list if it's a string.
        This allows both string and list to be accepted while ensuring
        the backend always receives a list.
        """
        if isinstance(value, str):
            return [value]
        return value

    def model_dump(self, *args, **kwargs):
        """Override model_dump to ensure id remains a list for serialization."""
        data = super().model_dump(*args, **kwargs)
        # Ensure id is always a list
        if isinstance(data.get("id"), str):
            data["id"] = [data["id"]]
        return data


class Explanation(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "channel_id": ["idxxx"],
                "tenant_id": "idxxx",
                "explanation_id": "idxxx",
                "type": "level_2",
                "label": "养老保险",
                "explanation": "养老保险是xxx",
                "alias": ["养老保险"],
            },
            "description": "为知识图谱中的标签添加解释说明",
        }
    )

    channel_id: List[str] = Field(description="渠道id", examples=[["idxxx", "idyyy"]])
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    type: LabelType = Field(
        description="标签类型", examples=["level_1", "level_2", "scene", "keywords"]
    )
    explanation_id: str = Field(
        "", description="解释说明id,用于区分不同的解释说明", examples=["idxxx"]
    )
    label: str = Field(
        description="待解释的标签,请确保label在知识图谱中存在", examples=["养老保险"]
    )
    explanation: str = Field(description="标签的具体解释", examples=["养老保险是xxx"])
    alias: List[str] = Field(
        default=[], description="别名，用于区分不同的解释说明", examples=["养老保险"]
    )

    @model_validator(mode="after")
    def validate(cls, values: "Explanation"):
        if not values.explanation_id:
            channel_id_str = "-".join(sorted(values.channel_id))
            explanation_id = create_hash_id(
                f"{values.type}-{values.label}-{channel_id_str}-{values.tenant_id}"
            )
            values.explanation_id = explanation_id

        return values


class AssistantInfo(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "assistant_id": "idxxx",
                "tenant_id": "idxxx",
                "channel_id": "idxxx",
                "agent_name": "助手名称",
                "position": "所在省市（区）",
                "service": "服务范围",
            },
            "description": "添加或更新助手信息",
        }
    )

    assistant_id: str = Field(description="助手id", examples=["idxxx"])
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    channel_id: str = Field(description="渠道id", examples=["idxxx"])
    agent_name: str = Field(description="助手名称", examples=["社保姐姐"])
    position: str = Field(description="所在省市（区）", examples=["广东省东莞市"])
    service: Optional[str] = Field(default=None, description="助手服务范围")


class Clarify(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "channel_id": ["idxxx"],
                "tenant_id": "idxxx",
                "clarify_id": "idxxx",
                "category": "参保人身份",
                "words": "请问你以企业职工、灵活就业人员、城乡居民、机关事业单位职工那种身份参保呢？",
            },
            "description": "添加或更新场景引导类别的澄清话术",
        }
    )

    channel_id: List[str] = Field(description="渠道id", examples=[["idxxx", "idyyy"]])
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    clarify_id: str = Field(
        "", description="澄清话术id,用于区分不同的澄清话术", examples=["idxxx"]
    )
    category: str = Field(
        "",
        description="场景引导类别,请确保该类别在知识图谱中存在",
        examples=["参保人身份"],
    )
    words: str = Field(
        description="参考澄清话术",
        examples=[
            "请问你以企业职工、灵活就业人员、城乡居民、机关事业单位职工那种身份参保呢？"
        ],
    )

    @model_validator(mode="after")
    def validate(cls, values: "Clarify"):
        if not values.clarify_id:
            channel_id_str = "-".join(sorted(values.channel_id))
            clarify_id = create_hash_id(
                f"{values.category}-{channel_id_str}-{values.tenant_id}"
            )
            values.clarify_id = clarify_id

        return values


class BaseResponse(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={"example": {"code": 200, "msg": "success"}}
    )

    code: int = Field(default=200, description="状态码")
    msg: str = Field(default="success", description="响应消息")


class QA(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "question": "社保征缴相关问题指引口径？",
                "answer": "您好，在办理社保缴费登记、申报社保缴费业务时如有疑问，可拨打0769-12366纳税缴费服务热线咨询...",
            }
        }
    )

    question: str = Field(description="问题文本")
    answer: str = Field(description="答案文本")


class RerankSimilarityRequest(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "text1": "社保征缴相关问题指引口径？",
                "text2": "社保缴费登记、申报社保缴费业务时如有疑问，可拨打0769-12366纳税缴费服务热线咨询",
            }
        }
    )

    text1: str = Field(description="第一个文本")
    text2: str = Field(description="第二个文本")


class RerankSimilarityResponse(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "similarity_score": 0.85,
            }
        }
    )

    similarity_score: float = Field(description="相似度得分")


class SimpleAnswerPrompt(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "prompt_id": "idxxx",
                "tenant_id": "idxxx", 
                "channel_id": ["idxxx"],
                "prompt": "请将以下答案整理成简洁明了的格式，保留关键信息，去除冗余内容。要求：1.保持答案的准确性；2.使用简洁的语言；3.突出重点信息；4.保持逻辑清晰。"
            },
            "description": "极简答案整理提示词"
        }
    )

    prompt_id: str = Field(description="提示词id", examples=["idxxx"])
    tenant_id: str = Field(description="租户id", examples=["idxxx"])
    channel_id: List[str] = Field(description="渠道id，可以有多个渠道", examples=[["idxxx"]])
    prompt: str = Field(description="极简答案整理提示词内容")

    @field_validator("channel_id", mode="before")
    def validate_channel_id(cls, value):
        if isinstance(value, str):
            return [value]
        return value


class DirectSimplifyAnswerRequest(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "prompt": "请将以下答案整理成简洁明了的格式，保留关键信息，去除冗余内容。要求：1.保持答案的准确性；2.使用简洁的语言；3.突出重点信息；4.保持逻辑清晰。",
                "detailed_answer": "您好，关于社保缴费的问题，我来为您详细解答..."
            },
            "description": "直接答案极简化处理请求，用于调试提示词"
        }
    )

    prompt: str = Field(description="极简答案整理提示词内容")
    detailed_answer: str = Field(description="需要进行极简化处理的详细答案")


class SimplifyAnswerRequest(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "tenant_id": "idxxx",
                "channel_id": "idxxx",
                "detailed_answer": "您好，关于社保缴费的问题，我来为您详细解答..."
            },
            "description": "答案极简化处理请求"
        }
    )

    tenant_id: Optional[str] = Field(None, description="租户id", examples=["idxxx"])
    channel_id: Optional[str] = Field(None, description="渠道id", examples=["idxxx"])
    prompt_id: Optional[str] = Field(None, description="提示词id", examples=["idxxx"])
    detailed_answer: str = Field(description="需要进行极简化处理的详细答案")

    @model_validator(mode="after")
    def validate_params(cls, values):
        if not values.prompt_id and not (values.tenant_id and values.channel_id):
            raise ValueError("请提供tenant_id+channel_id或prompt_id其中一种方式")
        if values.prompt_id and (values.tenant_id or values.channel_id):
            raise ValueError("tenant_id+channel_id和prompt_id不能同时使用")
        return values


class SimplifyAnswerResponse(BaseModel):
    model_config = ConfigDict(
        json_schema_extra={
            "example": {
                "code": 200,
                "msg": "success",
                "data": {
                    "simplified_answer": "社保缴费包括养老、医疗、失业、工伤、生育五险。缴费基数以上年度月平均工资为准，有上下限。用人单位和个人按规定比例缴费。如有问题可联系当地社保机构或拨打12333。"
                }
            }
        }
    )

    code: int = Field(default=200, description="状态码")
    msg: str = Field(default="success", description="响应消息")
    data: Optional[Dict] = Field(None, description="响应数据")
